/*-------------------------------------------------------------------------
 *
 * nodeNestloop.c
 *	  支持嵌套循环连接的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeNestloop.c
 *
 *-------------------------------------------------------------------------
 */
/*
 *	 接口例程
 *		ExecNestLoop	 - 处理两个计划的nestloop连接
 *		ExecInitNestLoop - 初始化连接
 *		ExecEndNestLoop  - 关闭连接
 */

#include "postgres.h"

#include "executor/execdebug.h"
#include "executor/nodeNestloop.h"
#include "miscadmin.h"
#include "utils/memutils.h"


/* ----------------------------------------------------------------
 *		ExecNestLoop(node)
 *
 * 旧注释
 *		返回与内外元组连接的元组，该元组满足资格子句。
 *
 *		它扫描内部关系以与当前外部元组连接。
 *
 *		如果没有找到，则从外部关系中获取下一个元组，
 *		重新从头开始扫描内部关系，以与外部元组连接。
 *
 *		如果所有剩余的外部元组都尝试过且都未与内部元组连接，
 *		则返回NULL。
 *
 *		如果内部关系没有元组，也返回NULL。
 *
 *		条件：
 *		  -- outerTuple包含来自外部关系的当前元组，右子节点（内部关系）
 *			 在先前返回的元组上保持“游标”。
 *			 通过在外部关系上保持扫描位置来实现这一点。
 *
 *		初始状态：
 *		  -- 外部子节点和内部子节点
 *			   准备返回第一个元组。
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_ExecNestLoop(PlanState *fc_pstate)
{
	NestLoopState *fc_node = castNode(NestLoopState, fc_pstate);
	NestLoop   *fc_nl;
	PlanState  *fc_innerPlan;
	PlanState  *fc_outerPlan;
	TupleTableSlot *fc_outerTupleSlot;
	TupleTableSlot *fc_innerTupleSlot;
	ExprState  *fc_joinqual;
	ExprState  *fc_otherqual;
	ExprContext *fc_econtext;
	ListCell   *fc_lc;

	CHECK_FOR_INTERRUPTS();

	/*
	 * 从节点获取信息
	 */
	ENL1_printf("getting info from node");

	fc_nl = (NestLoop *) fc_node->js.ps.plan;
	fc_joinqual = fc_node->js.joinqual;
	fc_otherqual = fc_node->js.ps.qual;
	fc_outerPlan = outerPlanState(fc_node);
	fc_innerPlan = innerPlanState(fc_node);
	fc_econtext = fc_node->js.ps.ps_ExprContext;

	/*
	 * 重置每个元组的内存上下文，以释放在上一个元组周期中分配的任何表达式评估
	 * 存储。
	 */
	ResetExprContext(fc_econtext);

	/*
	 * 好的，一切都已经为连接设置好了，所以现在循环直到我们返回一个
	 * 合格的连接元组。
	 */
	ENL1_printf("entering main loop");

	for (;;)
	{
		/*
		 * 如果我们没有外部元组，获取下一个元组并重置
		 * 内部扫描。
		 */
		if (fc_node->nl_NeedNewOuter)
		{
			ENL1_printf("getting new outer tuple");
			fc_outerTupleSlot = ExecProcNode(fc_outerPlan);

			/*
			 * 如果没有更多的外部元组，则连接完成。
			 */
			if (TupIsNull(fc_outerTupleSlot))
			{
				ENL1_printf("no outer tuple, ending join");
				return NULL;
			}

			ENL1_printf("saving new outer tuple information");
			fc_econtext->ecxt_outertuple = fc_outerTupleSlot;
			fc_node->nl_NeedNewOuter = false;
			fc_node->nl_MatchedOuter = false;

			/*
			 * 获取必须传递给内部扫描的任何外部变量的值，并将它们存储在相应的PARAM_EXEC插槽中。
			 */
			foreach(fc_lc, fc_nl->nestParams)
			{
				NestLoopParam *fc_nlp = (NestLoopParam *) lfirst(fc_lc);
				int			fc_paramno = fc_nlp->paramno;
				ParamExecData *fc_prm;

				fc_prm = &(fc_econtext->ecxt_param_exec_vals[fc_paramno]);
				/* 参数值应为OUTER_VAR变量 */
				Assert(IsA(fc_nlp->paramval, Var));
				Assert(fc_nlp->paramval->varno == OUTER_VAR);
				Assert(fc_nlp->paramval->varattno > 0);
				fc_prm->value = slot_getattr(fc_outerTupleSlot,
										  fc_nlp->paramval->varattno,
										  &(fc_prm->isnull));
				/* 将参数值标记为已更改 */
				fc_innerPlan->chgParam = bms_add_member(fc_innerPlan->chgParam,
													 fc_paramno);
			}

			/*
			 * 现在重新扫描内部计划
			 */
			ENL1_printf("rescanning inner plan");
			ExecReScan(fc_innerPlan);
		}

		/*
		 * 我们有一个外部元组，尝试获取下一个内部元组。
		 */
		ENL1_printf("getting new inner tuple");

		fc_innerTupleSlot = ExecProcNode(fc_innerPlan);
		fc_econtext->ecxt_innertuple = fc_innerTupleSlot;

		if (TupIsNull(fc_innerTupleSlot))
		{
			ENL1_printf("no inner tuple, need new outer tuple");

			fc_node->nl_NeedNewOuter = true;

			if (!fc_node->nl_MatchedOuter &&
				(fc_node->js.jointype == JOIN_LEFT ||
				 fc_node->js.jointype == JOIN_ANTI))
			{
				/*
				 * 我们正在进行外连接，并且此外部元组没有连接匹配。
				 * 生成一个假连接元组，内部元组为null， 
				 * 如果它通过了非连接资格，则返回它。
				 */
				fc_econtext->ecxt_innertuple = fc_node->nl_NullInnerTupleSlot;

				ENL1_printf("testing qualification for outer-join tuple");

				if (fc_otherqual == NULL || ExecQual(fc_otherqual, fc_econtext))
				{
					/*
					 * 资格被满足，所以我们投影并返回
					 * 使用ExecProject()返回结果元组的插槽。
					 */
					ENL1_printf("qualification succeeded, projecting tuple");

					return ExecProject(fc_node->js.ps.ps_ProjInfo);
				}
				else
					InstrCountFiltered2(fc_node, 1);
			}

			/*
			 * 否则只需返回循环顶部，获取新的外部元组。
			 */
			continue;
		}

		/*
		 * 此时我们有一对新的内部和外部元组，所以我们
		 * 测试内部和外部元组以查看它们是否满足节点的
		 * 资格。
		 *
		 * 只有连接资格决定MatchedOuter状态，但要实际返回元组
		 * 所有资格都必须通过。
		 */
		ENL1_printf("testing qualification");

		if (ExecQual(fc_joinqual, fc_econtext))
		{
			fc_node->nl_MatchedOuter = true;

			/* 在反连接中，我们从不返回匹配的元组 */
			if (fc_node->js.jointype == JOIN_ANTI)
			{
				fc_node->nl_NeedNewOuter = true;
				continue;		/* 返回循环顶部 */
			}

			/*
			 * 如果我们只需要与第一个匹配的内部元组连接，那么
			 * 考虑返回这个，但之后继续下一个
			 * 外部元组。
			 */
			if (fc_node->js.single_match)
				fc_node->nl_NeedNewOuter = true;

			if (fc_otherqual == NULL || ExecQual(fc_otherqual, fc_econtext))
			{
				/*
				 * 资格被满足，所以我们投影并返回
				 * 使用ExecProject()返回结果元组的插槽。
				 */
				ENL1_printf("qualification succeeded, projecting tuple");

				return ExecProject(fc_node->js.ps.ps_ProjInfo);
			}
			else
				InstrCountFiltered2(fc_node, 1);
		}
		else
			InstrCountFiltered1(fc_node, 1);

		/*
		 * 元组未通过条件，所以释放每个元组的内存并重试。
		 */
		ResetExprContext(fc_econtext);

		ENL1_printf("qualification failed, looping");
	}
}

/* ----------------------------------------------------------------
 *		ExecInitNestLoop
 * ----------------------------------------------------------------
 */
NestLoopState *
ExecInitNestLoop(NestLoop *fc_node, EState *fc_estate, int fc_eflags)
{
	NestLoopState *fc_nlstate;

	/* 检查不支持的标志 */
	Assert(!(fc_eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));

	NL1_printf("ExecInitNestLoop: %s\n",
			   "initializing node");

	/*
	 * 创建状态结构
	 */
	fc_nlstate = makeNode(NestLoopState);
	fc_nlstate->js.ps.plan = (Plan *) fc_node;
	fc_nlstate->js.ps.state = fc_estate;
	fc_nlstate->js.ps.ExecProcNode = fc_ExecNestLoop;

	/*
	 * 杂项初始化
	 *
	 * 为节点创建表达式上下文
	 */
	ExecAssignExprContext(fc_estate, &fc_nlstate->js.ps);

	/*
	 * 初始化子节点
	 *
	 * 如果我们没有要从外部传递给内部关系的参数，
	 * 告知内部子节点，便宜的重新扫描会很好。如果我们确实有
	 * 这样的参数，那么在内部子节点中根本没有支持REWIND的意义，
	 * 因为它将始终使用新的参数值重新扫描。
	 */
	outerPlanState(fc_nlstate) = ExecInitNode(outerPlan(fc_node), fc_estate, fc_eflags);
	if (fc_node->nestParams == NIL)
		fc_eflags |= EXEC_FLAG_REWIND;
	else
		fc_eflags &= ~EXEC_FLAG_REWIND;
	innerPlanState(fc_nlstate) = ExecInitNode(innerPlan(fc_node), fc_estate, fc_eflags);

	/*
	 * 初始化结果槽、类型和投影。
	 */
	ExecInitResultTupleSlotTL(&fc_nlstate->js.ps, &TTSOpsVirtual);
	ExecAssignProjectionInfo(&fc_nlstate->js.ps, NULL);

	/*
	 * 初始化子表达式
	 */
	fc_nlstate->js.ps.qual =
		ExecInitQual(fc_node->join.plan.qual, (PlanState *) fc_nlstate);
	fc_nlstate->js.jointype = fc_node->join.jointype;
	fc_nlstate->js.joinqual =
		ExecInitQual(fc_node->join.joinqual, (PlanState *) fc_nlstate);

	/*
	 * 检测是否只需考虑第一个匹配的内层元组
	 */
	fc_nlstate->js.single_match = (fc_node->join.inner_unique ||
								fc_node->join.jointype == JOIN_SEMI);

	/* 如有需要，设置外连接的空元组 */
	switch (fc_node->join.jointype)
	{
		case JOIN_INNER:
		case JOIN_SEMI:
			break;
		case JOIN_LEFT:
		case JOIN_ANTI:
			fc_nlstate->nl_NullInnerTupleSlot =
				ExecInitNullTupleSlot(fc_estate,
									  ExecGetResultType(innerPlanState(fc_nlstate)),
									  &TTSOpsVirtual);
			break;
		default:
			elog(ERROR, "unrecognized join type: %d",
				 (int) fc_node->join.jointype);
	}

	/*
	 * 最后，清除当前的外部元组。
	 */
	fc_nlstate->nl_NeedNewOuter = true;
	fc_nlstate->nl_MatchedOuter = false;

	NL1_printf("ExecInitNestLoop: %s\n",
			   "node initialized");

	return fc_nlstate;
}

/* ----------------------------------------------------------------
 *		ExecEndNestLoop
 *
 *		关闭扫描并释放分配的存储
 * ----------------------------------------------------------------
 */
void ExecEndNestLoop(NestLoopState *fc_node)
{
	NL1_printf("ExecEndNestLoop: %s\n",
			   "ending node processing");

	/*
	 * 释放表达式上下文
	 */
	ExecFreeExprContext(&fc_node->js.ps);

	/*
	 * 清空元组表
	 */
	ExecClearTuple(fc_node->js.ps.ps_ResultTupleSlot);

	/*
	 * 关闭子计划
	 */
	ExecEndNode(outerPlanState(fc_node));
	ExecEndNode(innerPlanState(fc_node));

	NL1_printf("ExecEndNestLoop: %s\n",
			   "node processing ended");
}


void ExecReScanNestLoop(NestLoopState *fc_node)
{
	PlanState  *fc_outerPlan = outerPlanState(fc_node);

	/*
	 * 如果 outerPlan->chgParam 不是 null，则计划将通过第一个 ExecProcNode 自动重新扫描。
	 */
	if (fc_outerPlan->chgParam == NULL)
		ExecReScan(fc_outerPlan);

	/*
	 * innerPlan 对于每个新的外部元组都会重新扫描，且此处必须不重新扫描，否则会在使用外部变量作为运行时键时出现内索引扫描的问题...
	 */

	fc_node->nl_NeedNewOuter = true;
	fc_node->nl_MatchedOuter = false;
}
